import type { AxiosResponse, AxiosResponseHeaders, RawAxiosRequestHeaders } from 'axios';
import axios, { CanceledError } from 'axios';
import qs from 'qs';

import ApiResultCode from '@/fierce/request/api-result-code';
import RequestHeader from '@/fierce/request/request-header';
import CommonPage from '@/fierce/pages/common-page';
import CommonPageName from '@/fierce/pages/common-page-name';
import router from '@/fierce/router';
import { usePageDataStore } from '@/fierce/stores/page-data';
import { getAppConfig } from '@/fierce/utils/env-util';
import { useAuthenticationStore } from '@/fierce/stores/authentication';

const instance = axios.create({
  baseURL: getAppConfig().apiBaseUrl,
  timeout: 120000,
  headers: {
    [RequestHeader.CONTENT_TYPE]: RequestHeader.CONTENT_TYPE_APPLICATION_JSON,
  },
  paramsSerializer: {
    serialize: params => qs.stringify(params, { arrayFormat: 'brackets', skipNulls: true }),
  },
  responseType: 'json',
  validateStatus: () => true,
});

let authenticationStore: Nullable<ReturnType<typeof useAuthenticationStore>> = null;
// 匹配uri参数的正则对象
const uriParamReg = /\{(\w+)\}/;

instance.interceptors.request.use(
  config => {
    if (authenticationStore == null) {
      authenticationStore = useAuthenticationStore();
    }
    if (authenticationStore.isLoggedIn && config.headers) {
      config.headers[RequestHeader.AUTHORIZATION] = `Bearer ${authenticationStore.accessToken}`;
    }

    // 处理URI参数
    const params = config.params ?? {};
    let url = decodeURIComponent(config.url as string);
    let matched = uriParamReg.exec(url);
    while (matched) {
      url = url.replace(matched[0], params[matched[1]] ?? '');
      delete params[matched[1]];
      matched = uriParamReg.exec(url);
    }
    config.url = url;

    return config;
  },
  error => {
    const { message } = error;
    // errorMessage(`发起请求异常：${message}！`);
    return Promise.reject(message);
  },
);

instance.interceptors.response.use(
  (response: AxiosResponse<ApiResponse>) => {
    if (response.config.responseType === 'blob') {
      return response;
    }

    const data = response.data;
    if (!data) {
      handleApiError(
        response.status,
        response.statusText,
        response.config.method as string,
        response.config.url as string,
      ).catch((e: any) => {
        console.error(`Api错误处理失败：${e}`);
        // errorMessage(`Api错误处理失败：${e}`);
      });

      return Promise.reject({
        code: response.status,
        message: response.statusText,
      });
    }

    const { code, message } = data;
    if (code === ApiResultCode.SUCCESS) {
      return response;
    }

    handleApiError(code, message, response.config.method as string, response.config.url as string)
      .catch((e: any) => {
      // errorMessage(`Api错误处理失败：${e}`);
        console.error(`Api错误处理失败：${e}`);
      });
    return Promise.reject(data);
  },
  error => {
    if (error instanceof CanceledError) {
      if (getAppConfig().showRapidDuplicateRequestWarning === 'true') {
        const canceledError = error as CanceledError<unknown>;
        // warningMessage(
        //   '请求已取消，此类情况一般由于段时间内发起了两次相同的请求导致，说明代码逻辑不合理，请根据Console错误堆栈排查解决！',
        // );
        console.log(canceledError.stack);
      }
      return Promise.reject();
    }

    const { response, message } = error;
    if (response?.data) {
      // 此处为处理响应信息时的逻辑错误
      // errorMessage(`处理请求响应信息异常：${message ?? '无'}！`);
      return Promise.reject();
    } else {
      // 处理提示信息
      let info = message;
      if (message === 'Network Error') {
        info = '接口连接异常！可能原因：网络不通或存在跨域问题。';
      }
      if (message.includes('timeout')) {
        info = '接口请求超时！';
      }
      if (message.includes('Request failed with status code')) {
        const code = message.substring(message.length - 3);
        info = `接口状态码异常：${code}！`;
      }
      // errorMessage(info || '接口未知异常！');

      console.warn(info);

      return Promise.reject();
    }
  },
);

const noRedirectRoutePaths = [CommonPage.FORBIDDEN, CommonPage.NOT_FOUND, CommonPage.INDEX];
/**
 * 处理Api异常
 *
 * @param code 异常嘛
 * @param message 异常消息
 * @param method 请求方法
 * @param uri 请求URI
 */
async function handleApiError(code: number, message: string, method: string, uri: string) {
  if (authenticationStore == null) {
    authenticationStore = useAuthenticationStore();
  }

  if (message.includes('系统未授权')) {
    code = 9999;
  }

  const routePath = usePageDataStore().currentPageFullPath;
  const pathTokens = routePath.split('?');
  switch (code) {
    case ApiResultCode.UNAUTHORIZED:
      console.warn('需要登录！');
      await router.replace({
        name: CommonPageName.LOGIN,
        query: noRedirectRoutePaths.includes(pathTokens[0] as CommonPage)
          ? undefined
          : {
              redirect: routePath,
            },
      });
      break;
    case ApiResultCode.ACCESS_DENIED:
      // warningMessage(`${message}请求：${method.toUpperCase()} - ${uri}，请联系管理员为您开通！`);
      console.warn(`${message}请求：${method.toUpperCase()} - ${uri}，请联系管理员为您开通！`);
      break;
    case ApiResultCode.TOKEN_EXPIRED:
      // infoMessage('登录失效！');
      // authenticationStore.clearAuthentication();
      await router.replace({
        name: CommonPageName.LOGIN,
        query: noRedirectRoutePaths.includes(pathTokens[0] as CommonPage)
          ? undefined
          : {
              redirect: routePath,
            },
      });
      break;
    case 9999:
      // errorMessage(message);
      await router.replace({
        name: CommonPageName.AUTHORIZATION,
      });
      break;
    case 503:
      // errorMessage('服务不可用，请检查后端服务是否启动成功或网络端口是否放开！');
      break;
    default:
      console.warn(message);
      // errorMessage(message);
  }
}

export default instance;

// 简化请求方法
export function get<T = any>(uri: string, params: Recordable = {}, headers: RawAxiosRequestHeaders = {}) {
  return instance
    .get<ApiResponse<T>>(uri, {
      headers,
      params,
    })
    .then(response => response.data);
}

export function post<T = any, D = Recordable>(
  uri: string,
  data: D,
  params: Recordable = {},
  headers: RawAxiosRequestHeaders = {},
) {
  return instance
    .post<ApiResponse<T>, AxiosResponse<ApiResponse<T>, D>, D>(uri, data, {
      headers,
      params,
    })
    .then(response => response.data);
}
export function filePost<T = any, D = Recordable>(
  uri: string,
  data: D,
  params: Recordable = {},
  headers: RawAxiosRequestHeaders = {},
) {
  return instance
    .post<ApiResponse<T>, AxiosResponse<ApiResponse<T>, D>, D>(uri, data, {
      headers,
      params,
      responseType: 'blob',
    })
    .then(response => response.data);
}

export function put<T = any, D = Recordable>(
  uri: string,
  data: D,
  params: Recordable = {},
  headers: RawAxiosRequestHeaders = {},
) {
  return instance
    .put<ApiResponse<T>, AxiosResponse<ApiResponse<T>, D>, D>(uri, data, {
      headers,
      params,
    })
    .then(response => response.data);
}

export function del<T = any>(uri: string, params: Recordable = {}, headers: RawAxiosRequestHeaders = {}) {
  return instance
    .delete<ApiResponse<T>>(uri, {
      headers,
      params,
    })
    .then(response => response.data);
}

async function doDownload(uri: string, params: Recordable, headers: RawAxiosRequestHeaders, method: 'get' | 'post') {
  if (method === 'get') {
    return await instance.get(uri, {
      headers,
      params,
      responseType: 'blob',
    });
  } else {
    return await instance.post(
      uri,
      {},
      {
        headers,
        params,
        responseType: 'blob',
      },
    );
  }
}

export async function download(
  uri: string,
  params: Recordable = {},
  filenameCustomizer: (name: string) => string = name => name,
  headers: RawAxiosRequestHeaders = {},
  method: 'get' | 'post' = 'get',
) {
  const { data, headers: responseHeaders } = await doDownload(uri, params, headers, method);

  try {
    const resp = JSON.parse(await data.text());
    console.warn(resp.message);
    // warningMessage(resp.message);
    return;
  } catch {
    /* empty */
  }
  const filename = filenameCustomizer(
    window.decodeURI(
      ((responseHeaders as AxiosResponseHeaders).get('content-disposition') as string)?.split('filename=')[1],
    ),
  );
  const objectUrl = window.URL.createObjectURL(new Blob([data]));
  const a = window.document.createElement('a');
  a.download = filename;
  a.href = objectUrl;
  a.click();
  // 回收
  window.URL.revokeObjectURL(objectUrl);
}
